<?php
// +----------------------------------------------------------------------
// | Step 工序服务
// +----------------------------------------------------------------------
// | 主要服务于Step数据处理
// +----------------------------------------------------------------------
// | 错误编码头 225xxx
// +----------------------------------------------------------------------
namespace common\service;

use common\exception\LogicException;
use common\model\AttributeModel;
use common\model\AttributeValueModel;
use common\model\ProjectModel;
use common\model\ProjectTemplateModel;
use common\model\StepCategoryAttributeModel;
use common\model\StepCategoryModel;
use common\model\StepModel;
use common\model\StepReviewWorkflowRelationModel;
use common\model\StepSkuModel;
use common\model\StepUpstreamModel;
use common\model\StepUpstreamOutsideModel;
use common\model\StepWorkflowModel;
use common\model\TaskModel;
use common\model\TaskRelationModel;
use Exception;
use support\bootstrap\Redis;
use support\ErrorCode;
use support\SingletonTrait;
use think\Cache;
use think\Tree;
use Throwable;
use Webman\Stomp\Client;

class StepService
{
    /**
     * @var StepModel
     */
    private $stepModel;
    private $stepCategoryModel;

    // 单例工具
    use SingletonTrait;

    public function __construct()
    {
        $this->stepModel = model(StepModel::class);
        $this->stepCategoryModel = model(StepCategoryModel::class);

    }

    /**
     * 查询工序
     * @param $stepId
     * @param string $fields
     * @return array
     */
    public function getStep($stepId, $fields = '*')
    {
        return model(StepModel::class)->cache(C('database.default_query_cache_time'))->field($fields)->find($stepId);
    }

    /**
     * 查询工序
     * @param $filter
     * @param $fields
     * @return array|false|mixed|string|null
     */
    public function getSteps($filter, $fields)
    {
        return model(StepModel::class)->where($filter)->field($fields)->cache(C('database.default_query_cache_time'))->select();
    }

    /**
     * 查询工序列表
     * @param $filter
     * @param $fields
     * @return array|false|mixed|string|null
     */
    public function getList($filter, $fields)
    {
        return model(StepModel::class)->where($filter)->field($fields)->cache(C('database.default_query_cache_time'))->select();
    }

    /**
     * 查询工序分类下所有工序列表
     * @param int $stepCategoryId
     * @return array|false|mixed|string
     */
    public function getStepCategoryStepList(int $stepCategoryId)
    {
        return model(StepModel::class)->field('id,name,code,step_category_id')->where(['step_category_id' => $stepCategoryId])->select();
    }

    /**
     * 获取工序分类从属字典数据列表
     * @return array
     */
    public function getStepCategoryList()
    {
        $stepCategoryModel = model(StepCategoryModel::class);
        $stepData = $stepCategoryModel
            ->field('id,name,code,parent_id,entity_id,is_standard,task_type,is_deprecate,show_name,show_index,is_enable_census enum,is_show,color')
            ->order('index asc,show_index asc')
            ->select();

        if (!empty($stepData)) {
            $tree = new Tree('id', 'parent_id');
            $tree->load($stepData);
            return $tree->DeepTree();
        }

        return [];
    }

    /**
     * 递归处理工序分类的属性子集
     * @param $stepCategoryAttributeValueDictSubset
     * @param $categoryTreeData
     * @param $stepCategoryAttributeValueDict
     * @param int $parentId
     */
    protected function recursionStepCategoryAttributeSubset(&$stepCategoryAttributeValueDictSubset, $categoryTreeData, $stepCategoryAttributeValueDict, $parentId = 0)
    {
        foreach ($categoryTreeData as $categoryTreeItem) {
            // 自己的
            $selfAttributeData = array_key_exists($categoryTreeItem['id'], $stepCategoryAttributeValueDict) ? $stepCategoryAttributeValueDict[$categoryTreeItem['id']] : [];

            // 父级的
            $parentAttributeData = array_key_exists($parentId, $stepCategoryAttributeValueDictSubset) ? $stepCategoryAttributeValueDictSubset[$parentId] : [];

            $mergeAttributeData = $parentAttributeData + $selfAttributeData;

            // 按照属性id升序排序
            ksort($mergeAttributeData);

            $stepCategoryAttributeValueDictSubset[$categoryTreeItem['id']] = $mergeAttributeData;

            if (!empty($categoryTreeItem['children'])) {
                $this->recursionStepCategoryAttributeSubset($stepCategoryAttributeValueDictSubset, $categoryTreeItem['children'], $stepCategoryAttributeValueDict, $categoryTreeItem['id']);
            }
        }
    }

    /**
     * 生成工序的SKU
     * @param int $insertStepCategoryId
     * @return array
     */
    public function generateStepSku($insertStepCategoryId = 0)
    {
        set_time_limit(0);
        if ($insertStepCategoryId === 0) {
            throw_strack_exception('Please generate by step category.', ErrorCode::PLEASE_GENERATE_BY_STEP_CATEGORY);
        }


        $stepCategoryModel = model(StepCategoryModel::class);
        $attributeModel = model(AttributeModel::class);
        $attributeValueModel = model(AttributeValueModel::class);
        $stepModel = model(StepModel::class);

        // 工序和属性映射
        $attributeData = $attributeModel->alias('attribute')
            ->join('LEFT JOIN step_category_attribute ON step_category_attribute.attribute_id = attribute.id')
            ->field('attribute.id as attribute_id,attribute.name as attribute_name,step_category_attribute.step_category_id')
            ->select();

        $attributeDict = array_to_dict($attributeData, 'step_category_id', 'attribute_id');

        // 属性值
        $attributeValueData = $attributeValueModel->field('id,value,attribute_id,variable')->select();

        $attributeValueDict = array_to_dict($attributeValueData, 'attribute_id', 'value');

        // 工序分类属性字典
        $stepCategoryAttributeValueDict = [];
        foreach ($attributeDict as $stepCategoryId => $attributeIds) {
            $attributeValues = [];
            foreach ($attributeIds as $attributeId) {
                $attributeValues[$attributeId] = $attributeValueDict[$attributeId];
            }
            $stepCategoryAttributeValueDict[$stepCategoryId] = $attributeValues;
        }

        // 所有工序分类
        $stepCategoryData = $stepCategoryModel->field('id,name,code,parent_id')->select();
        $stepCategoryIds = array_column($stepCategoryData, 'id');

        // 继承工序分类父级的属性
        $stepCategoryAttributeValueDictSubset = [];
        if (!empty($stepCategoryData)) {
            $tree = new Tree('id', 'parent_id');
            $tree->load($stepCategoryData);
            $categoryTreeData = $tree->DeepTree();
            $this->recursionStepCategoryAttributeSubset($stepCategoryAttributeValueDictSubset, $categoryTreeData, $stepCategoryAttributeValueDict);
        }


        // 获取每个工序分类下面的工序列表
        $stepCategoryStepList = $stepModel->field('id,name,code,step_category_id,variable')
            ->where(['step_category_id' => ['IN', join(',', $stepCategoryIds)]])
            ->select();

        $stepCategoryStepDict = array_to_dict($stepCategoryStepList, 'step_category_id');

        $stepVariableDict = array_column($stepCategoryStepList, 'variable', 'id');
        $attributeValueDict = array_column($attributeValueData, 'variable', 'value');
        // 生成SKU
        $stepSkuModel = model(StepSkuModel::class);

        if (array_key_exists($insertStepCategoryId, $stepCategoryStepDict)) {
            $stepSkuModel->startTrans();//强制开启事务array_intersect_key
            try {
                foreach ([$insertStepCategoryId => $stepCategoryStepDict[$insertStepCategoryId]] as $stepCategoryId => $stepData) {
                    $skuDict = [];
                    if (!empty($stepCategoryAttributeValueDictSubset[$stepCategoryId])) {
                        foreach ($stepData as $stepItem) {
                            $attributeList = array_values($stepCategoryAttributeValueDictSubset[$stepCategoryId]);
                            array_unshift($attributeList, [$stepItem['name']]);
                            $skuDict[$stepItem['id']] = combine_dika($attributeList);
                        }

                        // 写入数据库

                        foreach ($skuDict as $stepId => $skuItem) {
                            $stepItemAddRows = [];
                            $stepItemUpdateRows = [];

                            $step_manhour = $stepVariableDict[$stepId];
                            foreach ($skuItem as $sku) {
                                $sku_manhour = array_product(array_values(array_intersect_key($attributeValueDict, array_flip($sku))));
                                $manHour = number_floor_precision($step_manhour * $sku_manhour, 2);
                                $skuAddData = [
                                    'step_id' => $stepId,
                                    'uuid' => create_uuid(),
                                    'attribute_value' => join(':', $sku),
                                    'attribute_value_md5' => md5(join(':', $sku)),
                                    'man_hour' => $manHour
                                ];

                                $existId = $stepSkuModel->where(['attribute_value_md5' => $skuAddData['attribute_value_md5']])->getField('id');

                                if (empty($existId)) {
                                    $stepItemAddRows[] = $skuAddData;
                                } else {
                                    $skuAddData['id'] = $existId;
                                    $stepItemUpdateRows[] = $skuAddData;
                                    //$stepSkuModel->save($skuAddData);
                                }
                            }

                            if (!empty($stepItemAddRows)) {
                                $stepSkuModel->addAll($stepItemAddRows);
                            }

                            if (!empty($stepItemUpdateRows)) {
                                $stepSkuModel->saveAll($stepItemUpdateRows);
                            }
                        }
                    }
                }
                $stepSkuModel->commit();
            } catch (Throwable $e) {
                $stepSkuModel->rollback();
                throw_strack_exception($e->getMessage(), ErrorCode::GENERATE_STEP_SKU_FAILURE);
            }
        }

        return [];
    }

    /**
     * 保存工序sku工时
     * @param $data
     * @return array|void
     * @throws Exception
     */
    public function saveManHour($data)
    {
        $stepSkuModel = model(StepSkuModel::class);
        $stepSkuModel->startTrans();//强制开启事务
        try {
            $resData = [];
            foreach ($data as $item) {
                $addResData = $stepSkuModel->modifyItem($item);
                if (!$addResData) {
                    if ($stepSkuModel->getErrorCode() !== -411112) {
                        // 通用修改失败错误码
                        throw new LogicException($stepSkuModel->getError(), ErrorCode::SAVE_MANHOUR_FAILURE);
                    }
                } else {
                    // 返回成功数据
                    $resData[] = $addResData;
                }
            }
            $stepSkuModel->commit();
            return $resData;
        } catch (Throwable $e) {
            $stepSkuModel->rollback();
            throw_strack_exception($e->getMessage(), ErrorCode::SAVE_MANHOUR_FAILURE);
        }
    }

    /**
     * 绑定实体
     * @param $params
     * @return array
     * @throws Exception
     */
    public function bindEntityModule($params)
    {

        $stepCategoryModel = model(StepCategoryModel::class);

        $item = $stepCategoryModel->where(['id' => $params['data']['id']])->field("id,parent_id")->find();

        if (empty($item)) {
            throw new LogicException("can`t found step category", ErrorCode::STEP_CATEGORY_NOT_FOUND);
        }

        if ($item['parent_id'] > 0) {
            throw new LogicException("can`t bind entity_id when parent_id greater than 0", ErrorCode::ONLY_PARENT_CAN_BIND_ENTITY);
        }

        $resData = $stepCategoryModel->modifyItem($params['data']);

        if (!$resData) {
            // 修改工序分类失败错误码 - 002
            throw new LogicException($stepCategoryModel->getError(), ErrorCode::STEP_CATEGORY_MODIFY_FAILURE);
        }

        return success_response($stepCategoryModel->getSuccessMessage(), $resData);
    }

    /**
     * 获取工序分类和排期从属字典数据列表
     * @param $params
     * @return array
     */
    public function getStepCategoryPlan($params)
    {
        $showChild = $params['show_child'] ?? 0;
        $projectService = ProjectService::getInstance();
        $stepCategoryModel = model(StepCategoryModel::class);
        //项目数据
        $project = $projectService->getProject($params['filter']['project_id'], 'id,start_time,end_time,delivery_time,project_template_id');
        if (empty($project)) {
            throw_strack_exception("can`t found project", ErrorCode::PROJECT_NOT_FOUND);
        }

        $categoryData = $stepCategoryModel
            ->alias("sc")
            ->join('LEFT JOIN __PLAN__ AS p ON sc.id = p.step_category_id')
            ->field("sc.id,sc.parent_id,sc.name,p.start_time,p.end_time,p.id as plan_id")
            ->where(["p.project_id" => $project['id']])
            ->order('sc.index asc')
            ->select();

        $reNew = isset($params['renew_plan']) ? (int)$params['renew_plan'] : 0;
        if (empty($categoryData) || $reNew > 0) {
            try {
                $categoryData = $projectService->initProjectStepCategoryPlan($params['filter']['project_id']);
            } catch (Exception $e) {
                throw_strack_exception("init project plan failed[" . $e->getMessage() . "]", ErrorCode::INIT_PROJECT_PLAN_FAILURE);
            }
        }

        foreach ($categoryData as &$category) {
            $this->formatGanttItem($category, 'startnoearlierthan');
        }
        if (!empty($categoryData)) {
            $tree = new Tree('id', 'parent_id');
            $tree->load($categoryData);
            $categoryData = $tree->DeepTree();
        }

        if ($showChild <= 0) {
            /**
             *  隐藏二级分类
             */
            $categoryData = array_map(function ($item) {
                if (array_key_exists('children', $item)) {
                    unset($item['children']);
                }
                return $item;
            }, $categoryData);
        }

        return $this->formatStepCategoryPlanData($categoryData, $project);
    }


    /**
     * 节点是否在ids里
     * @param $dataNow
     * @param $cateIds
     * @return bool
     */
    public function childrenInIds(&$dataNow, &$cateIds)
    {
        if (empty($dataNow)) {
            //为空 说明当前节点不存在
            return false;
        }

        if (in_array($dataNow['id'], $cateIds)) { //当前节点存在ids里
            return true;
        }

        if (empty($dataNow['children'])) {
            return false;
        }

        foreach ($dataNow['children'] as &$child) {
            if ($this->childrenInIds($child, $cateIds)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 遍历树
     * @param $tree
     * @param $cateIds
     * @param null $callBack
     */
    public function traverseStepCategoryTree(&$tree, $cateIds, $callBack = null)
    {
        foreach ($tree as $key => &$item) {
            if (!$this->childrenInIds($item, $cateIds)) {
                unset($tree[$key]);
                continue;
            }
            if (is_callable($callBack)) {
                $callBack($item);
            }
        }
        //处理key被删掉之后 key的连续性被破坏 导致json_encode array=>json
        $tree = array_values($tree);
        foreach ($tree as $key => &$item) {
            if (empty($item['children'])) {
                continue;
            }
            $this->traverseStepCategoryTree($item['children'], $cateIds, $callBack);
        }

    }

    /**
     * 格式化甘特图数据
     * @param $stepCateData
     * @param $projectData
     * @param array $dependencies
     * @param array $timeRanges
     * @param array $events
     * @param array $resources
     * @return array
     */
    public static function formatStepCategoryPlanData($stepCateData, $projectData, $dependencies = [], $timeRanges = [], $events = [], $resources = [])
    {
        $res = [
            'success' => true,
            'project' => [
                'calendar' => "general",
                'startDate' => get_format_date(empty($projectData['start_time']) ? time() : $projectData['start_time'], 0),
                'endDate' => get_format_date(empty($projectData['end_time']) ? strtotime("+2 years") : $projectData['end_time'], 0),
            ],
            'calendars' => [
                "rows" => [],
            ],
            'tasks' => [
                "rows" => $stepCateData
            ],
            'dependencies' => [
                "rows" => $dependencies,
            ],
            "resources" => [
                "rows" => $resources,
            ],
            "assignments" => [
                "rows" => [],
            ],
            "timeRanges" => [
                'rows' => $timeRanges
            ],
            "events" => [
                "rows" => $events
            ]
        ];
        if (isset($projectData['delivery_time']) && $projectData['delivery_time'] > 0) {
            $res['timeRanges']['rows'][] = [
                "name" => "截至日期",
                "startDate" => date("Y-m-d", $projectData['delivery_time']),
                "duration" => 0,
                "durationUnit" => "d",
                "cls" => "b-fa b-fa-diamond",
            ];
        }

        $index = 0;
        $res['timeRanges']['rows'] = array_map(function ($item) use (&$index) {
            $index++;
            $item['id'] = $index;
            return $item;
        }, $res['timeRanges']['rows']);


        return $res;
    }


    /**
     * 获得一颗工序分类树并且填充工序
     * @return array
     */
    public function getAllStepTree($param)
    {
        $needStep = $param['step_needed'] ?? 1;
        $needStep = (int)$needStep !== 0;
        $needTotal = $param['total_needed'] ?? 0;
        $totalText = $param['total_text'] ?? "全部";
        $needTotal = (int)$needTotal === 1;
        $withOutTopStepCategory = $param['with_out_top'] ?? 0;
        $withOutTopStepCategory = $withOutTopStepCategory == 1;
        $stepModel = model(StepModel::class);

        $cateTree = $this->getStepCategoryList();
        if ($needStep) {
            $allStep = $stepModel->where(['step_category_id' => ['gt', 0]])
                ->field("id,name,code,color,step_category_id")->select();

            $this->traverseStepCateTree($cateTree, function (&$node) use (&$allStep) {
                if (isset($node['step_category_id'])) {
                    return;
                }
                $step_cate_id = $node['id'];
                //筛选出当前分类的工序数据 合并到children中
                $stepChildren = array_filter($allStep, function ($item) use ($step_cate_id) {
                    return $item['step_category_id'] == $step_cate_id;
                });
                $node['children'] = array_merge(!empty($node['children']) ? $node['children'] : [], $stepChildren);
            });
        }
        if ($withOutTopStepCategory) {
            $cateTreeCopy = [];
            $this->traverseStepCateTree($cateTree, function (&$node) use (&$cateTreeCopy) {
                if (isset($node['step_category_id'])) {
                    return;
                }
                if ($node['parent_id'] > 0) {
                    $cateTreeCopy[] = $node;
                }
            });
            $cateTree = $cateTreeCopy;
        }

        if ($needTotal) {
            $cateTree = [
                "id" => 0,
                'name' => $totalText,
                "children" => $cateTree
            ];
        }

        return $cateTree;
    }

    /**
     * 遍历工序分类树
     * @param $tree
     * @param $callBack
     * @return void
     */
    public function traverseStepCateTree(&$tree, $callBack)
    {
        //节点数据为空 直接结束
        if (empty($tree)) {
            return;
        }
        foreach ($tree as $key => &$value) {
            //执行节点回调
            $callBack($value);

            //子节点空 直接跳过
            if (empty($value['children'])) {
                continue;
            }
            //递归子节点
            $this->traverseStepCateTree($value['children'], $callBack);
        }
    }

    /**
     * 选择工序数据 包含分类的上级分类信息
     * @param $params
     * @return array
     */
    public function selectWithParentCate($params)
    {
        $stepModel = model(StepModel::class);
        $stepCateModel = model(StepCategoryModel::class);
        $fields = explode(',', $params['param']['fields']);
        if (!in_array('step_category.parent_id', $fields)) {
            $fields[] = "step_category.parent_id";
        }
        $params['param']['fields'] = implode(',', $fields);


        $steps = $stepModel->selectData($params['param']);

        $parent_ids = array_column(array_column($steps['rows'], 'step_category'), 'parent_id', 'parent_id');

        $parentsCate = $stepCateModel->where(['id' => ['IN', join(',', $parent_ids)]])->field('id,name')->select();
        $parentsCate = array_column($parentsCate, null, 'id');

        foreach ($steps['rows'] as &$row) {
            if (isset($row['step_category']) && $row['step_category']['parent_id'] > 0 && isset($parentsCate[$row['step_category']['parent_id']])) {
                $row['step_category_parent'] = $parentsCate[$row['step_category']['parent_id']];
            }
        }

        return $steps;
    }

    /**
     * 格式化甘特行数据
     * @param $item
     * @param null $constraintType
     */
    public static function formatGanttItem(&$item, $constraintType = null, $rowsType = "", $isEntityJsonNeeded = true)
    {
        switch ($rowsType) {
            case "event":
                $item['startDate'] = get_format_date($item['start_time'], 1);
                $item['endDate'] = get_format_date($item['end_time'], 1);
                $item['duration'] = number_floor_precision(($item['end_time'] - $item['start_time']) / (3600 * 24), 2);
                $item['durationUnit'] = "day";
                break;
            default:
                if (isset($item['start_time']) && isset($item['end_time'])) {
                    if (empty($item['start_time']) || empty($item['end_time'])) {
//                        $item['duration'] = 0;
//                        $item['startDate'] = "";
//                        $item['endDate'] = "";
                        $item['constraintType'] = $constraintType;
                        if ($item['constraintType'] == 'startnoearlierthan' && !empty($item['startDate'])) {
                            $item['constraintDate'] = $item['startDate'];
                        }

                    } else {
                        $item['startDate'] = get_format_date($item['start_time'], 1);
                        $item['endDate'] = get_format_date($item['end_time'], 1);
                        $item['duration'] = number_floor_precision(($item['end_time'] - $item['start_time']) / (3600 * 24), 2);
                        $item['constraintType'] = $constraintType;
                        if ($item['constraintType'] == 'startnoearlierthan') {
                            $item['constraintDate'] = $item['startDate'];
                        }

                    }
                }
                $item['percentDone'] = $item['percentDone'] ?? 0;
                $item['expanded'] = $item['expanded'] ?? true;
                $item['rollup'] = true;
        }

        if ($isEntityJsonNeeded) {
            $item['asset_category'] = $item['asset_category'] ?? "";
            $item['asset_grade'] = $item['asset_grade'] ?? "";
            $item['design_category'] = $item['design_category'] ?? "";
            $item['design_grade'] = $item['design_grade'] ?? "";
            $item['level_grade'] = $item['level_grade'] ?? "";
            $item['executor_name'] = $item['executor_name'] ?? "";
        }
    }

    /**
     * 获得工序树
     * @return array|bool
     */
    public function selectStepsWithCate()
    {
        $stepModel = model(StepModel::class);
        $stepCateModel = model(StepCategoryModel::class);
        $categories = $stepCateModel->field('id,name,parent_id')->select();
        $tree = new Tree('id', 'parent_id');
        $tree->load($categories);
        $res = $tree->DeepTree();
        $steps = $stepModel->field("id,name,code,color,step_category_id")->select();
        $this->traverseStepCateTree($res, function (&$item) use (&$steps) {
            if (isset($item['step_category_id'])) {
                return;
            }
            $item['children'] = array_merge($item['children'] ?? [], array_filter($steps, function ($step) use (&$item) {
                return $step['step_category_id'] == $item['id'];
            }));
        });
        return $res;
    }

    /**
     * 批量绑定工序流
     * @param $dataList
     * @return array
     * @throws Throwable
     */
    public function batchBindReviewWorkflow($dataList)
    {
        $dataGrouped = array_group_by($dataList, 'project_template_id', 'step_workflow_id', 'step_id');
        $res = [];

        foreach ($dataGrouped as $projectTemplateId => $groupedByWorkflowId) {
            foreach ($groupedByWorkflowId as $stepWorkflowId => $groupedByStepId) {
                foreach ($groupedByStepId as $stepId => $data) {
                    $reviewConfigList = model(StepReviewWorkflowRelationModel::class)->where(['project_template_id' => $projectTemplateId, 'step_workflow_id' => $stepWorkflowId, 'step_id' => $stepId])->select();
                    $reviewConfigList = array_column($reviewConfigList, null, 'attr_str');
                    foreach ($data as $item) {
                        if (isset($reviewConfigList[$item['attr_str']])) {
                            $relationId = $reviewConfigList[$item['attr_str']]['id'];
                            $flag = model(StepReviewWorkflowRelationModel::class)->modifyItem([
                                'id' => $relationId,
                                'review_workflow_id' => $item['review_workflow_id'],
                            ]);
                        } else {
                            $flag = model(StepReviewWorkflowRelationModel::class)->addItem($item);
                        }
                        if ($flag === false) {
                            $errCode = model(StepReviewWorkflowRelationModel::class)->getErrorCode();
                            if ($errCode != \think\exception\ErrorCode::NO_DATA_HAS_BEEN_CHANGED) {
                                throw_strack_exception(model(StepReviewWorkflowRelationModel::class)->getError(), model(StepReviewWorkflowRelationModel::class)->getErrorCode());
                            }
                        }
                        $res[] = $flag;
                    }
                }
            }
        }

        return $res;
    }

    /**
     * 关联的审批流 分类工序树
     * @return array|bool
     */
    public function selectReview()
    {
        $res = $this->selectStepsWithCate();
        $stepReviewRelationModel = model(StepReviewWorkflowRelationModel::class);
        $relationData = $stepReviewRelationModel
            ->alias("r")
            ->join("__STEP__ AS s ON r.step_id = s.id", 'LEFT')
            ->join("__REVIEW_WORKFLOW__ AS rw on r.review_workflow_id = rw.id", "LEFT")
            ->where("s.id IS NOT NULL")
            ->field("r.step_id,r.review_workflow_id,rw.name,rw.description")
            ->select();
        $relationData = array_column($relationData, null, 'step_id');
//        return $relationData;
        $this->traverseStepCateTree($res, function (&$item) use ($relationData) {
            if (isset($item['step_category_id'])) {
                //只处理工序
                $item['review_workflow'] = $relationData[$item['id']] ?? [];
            }
        });
        return $res;
    }

    /**
     * 更新工序分类排序索引
     * @param $data
     * @return array
     * @throws Exception
     * @throws Throwable
     */
    public function setStepCategoryIndex($data)
    {
        $updateData = [];

        $stepCategoryModel = model(StepCategoryModel::class);
        $stepCategoryModel->startTrans();
        try {
            foreach ($data as $item) {
                if (array_key_exists('id', $item) && array_key_exists('index', $item)) {
                    $updateData[] = $item;
                    $stepCategoryModel->save($item);
                } else {
                    throw new LogicException('Incorrect data format.', ErrorCode::INCORRECT_DATA_FORMAT);
                }
            }
            $stepCategoryModel->commit();
        } catch (Throwable $exception) {
            $stepCategoryModel->rollback();
            throw $exception;
        }

        return $updateData;
    }

    /**
     * 获得当前节点所有child
     * @param $node
     * @param $res
     */
    public function getNodeChildren(&$node, &$res)
    {
        if (isset($node['children'])) {
            foreach ($node['children'] as $child) {
                $res[] = $child['id'];
                $this->getNodeChildren($child, $res);
            }
        }
    }

    public function getStepCategoryAttrList($stepCategoryId)
    {
        // 所有工序分类
        $stepCategoryData = model(StepCategoryModel::class)->field('id,name,code,parent_id')->select();

        // 继承工序分类父级的属性
        $tree = new Tree('id', 'parent_id');
        $tree->load($stepCategoryData);
        $categoryTreeData = $tree->DeepTree();
        $topCategoryId = 0;
        $findCategoryId = $stepCategoryId;
        $tree->traverseTree($categoryTreeData, function ($item) use (&$topCategoryId, &$findCategoryId) {
            if ($item['id'] == $findCategoryId) {
                $topCategoryId = $findCategoryId = $item['parent_id'];
                if ($findCategoryId == 0) {
                    $topCategoryId = $item['id'];
                }
            }
        });
        return model(StepCategoryAttributeModel::class)
            ->join("attribute_value ON attribute_value.attribute_id = step_category_attribute.attribute_id", 'LEFT')
            ->where(['step_category_attribute.step_category_id' => $topCategoryId])
            ->field("attribute_value.attribute_id,attribute_value.value,attribute_value.variable")
            ->select();
    }

    /**
     * 生成工序的SKU 通过工序id
     * @param int $stepId
     * @return array
     * @throws Exception
     */
    public function generateStepSkuByStepId($stepId = 0)
    {
        $redisBatchNumber = 'execute_generate_step_sku;step_id_' . $stepId;
        if (Redis::lLen($redisBatchNumber)) {
            // 允许重复生成 但是重复生成 会清除之前的redis sku数据
            Redis::del($redisBatchNumber);
        }
        $step = model(StepModel::class)->where(['id' => $stepId])->find();
        // 属性字典
        $attrList = $this->getStepCategoryAttrList($step['step_category_id']);
        $attrNameList = [];
        foreach ($attrList as $attr) {
            $attrNameList[$attr['attribute_id']][] = $attr['value'];
        }
        ksort($attrNameList);
        $attrNameList = array_values($attrNameList);
        array_unshift($attrNameList, [$step['name']]);
        $skuItem = [];
        $sep = ':';
        dika_string_recursion($attrNameList, $skuItem, $sep);

        // 拼接数据
        $batchNumber = 1000;
        $pd = [];
        $pdi = 0;
        foreach ($skuItem as $sku) {
            $pd[] = $sku;
            $pdi += 1;
            if ($pdi >= 100) {
                Redis::lPush($redisBatchNumber, ...$pd);
                $pdi = 0;
                $pd = [];
            }
        }
        if (!empty($pd)) {
            Redis::lPush($redisBatchNumber, ...$pd);
        }

        Redis::expire($redisBatchNumber, 60 * 60 * 12);
        Client::send('execute_generate_step_sku', build_queue_data_with_xu(['data_key' => $redisBatchNumber, 'step_id' => $stepId, 'sep' => $sep, 'batch_number' => $batchNumber]), 3);

        return [];
    }

    /**
     * 加入到队列
     * @param $stepId
     * @param $delay
     */
    protected function appendStepSkuToQueue($stepId, $delay)
    {
        Client::send('generate_step_sku', build_queue_data_with_xu([
            'step_id' => $stepId,
        ]), $delay);
    }

    /**
     * 执行生成step sku
     * @param $data
     */
    public function executeGenerateStepSku($data)
    {
        $stepItemAddRows = [];
        $stepItemUpdateRows = [];

        $stepSkuExitList = model(StepSkuModel::class)->field('id,attribute_value_md5')->where(['attribute_value_md5' => ['IN', join(',', array_column($data, 'attribute_value_md5'))]])->select();
        $stepSkuExitMap = array_column($stepSkuExitList, 'id', 'attribute_value_md5');

        foreach ($data as $item) {
            if (array_key_exists($item['attribute_value_md5'], $stepSkuExitMap)) {
                $item['id'] = $stepSkuExitMap[$item['attribute_value_md5']];
                $stepItemUpdateRows[] = $item;
            } else {
                $stepItemAddRows[] = $item;
            }
        }

        try {
            if (!empty($stepItemAddRows)) {
                model(StepSkuModel::class)->addAll($stepItemAddRows);
            }
            if (!empty($stepItemUpdateRows)) {
                model(StepSkuModel::class)->saveAll($stepItemUpdateRows);
            }
        } catch (Exception $e) {
            trace(format_exception_info($e), 'ERR');
        }
    }


    /**
     * 通过属性值id获取sku ids
     * @param $attributeIds
     * @return string
     */
    public function getStepSkuIdsByStepAttributeIds($attributeIds)
    {
//        $test = [
//            "step"=> [
//                "5"=> [
//                    "attribute"=> [
//                        "1"=> [
//                            "value"=> "1,2,3"
//                        ],
//                        "2"=> [
//                            "value"=> "4,5,6,7"
//                        ],
//                        "3"=> [
//                            "value"=> "61,10"
//                        ],
//                        "5"=> [
//                            "value"=> "23,24"
//                        ]
//                    ]
//                ],
//                "7"=> [
//                    "attribute"=> [
//                        "1"=> [
//                            "value"=> "1,2,3"
//                        ],
//                        "3"=> [
//                            "value"=> "8,9,10,61"
//                        ]
//                    ]
//                ]
//            ]
//        ];

        // 获取工序字典
        $stepModel = model(StepModel::class);
        $stepData = $stepModel->field('id,name')->select();
        $stepDict = array_column($stepData, 'name', 'id');

        // 属性值字典
        $attributeValueModel = model(AttributeValueModel::class);
        $attributeData = $attributeValueModel->field('id,value')->select();
        $attributeValueDict = array_column($attributeData, 'value', 'id');

        // 组装sku，属性组装sku 按照id升序排序
        $stepAttrNameList = [];
        foreach ($attributeIds as $stepId => $attribute) {
            $attrNameList = [];
            foreach ($attribute['attribute'] as $attributeId => $values) {
                $attrNameList[$attributeId] = [];
                $valueIds = explode(',', $values['value']);
                foreach ($valueIds as $valueId) {
                    $attrNameList[$attributeId][] = $attributeValueDict[$valueId];
                }
            }

            ksort($attrNameList);
            $stepAttrNameList[$stepId] = $attrNameList;
        }

        $attributeSkus = [];
        foreach ($stepAttrNameList as $stepId => $stepAttrNameItem) {
            $stepAttrNameItems = array_values($stepAttrNameItem);
            array_unshift($stepAttrNameItems, [$stepDict[$stepId]]);

            $skuItems = combine_dika($stepAttrNameItems);
            foreach ($skuItems as $sku) {
                $attributeSkus[] = md5(join(':', $sku));
            }
        }

        $stepSkuIds = '';
        if (count($attributeSkus) > 0) {
            $stepSkuModel = model(StepSkuModel::class);
            $trunkSize = 2000;
            $attributeSkusList = array_chunk($attributeSkus, $trunkSize);
            unset($attributeSkus);

            foreach ($attributeSkusList as $attributeSkus) {
                $stepSkuData = $stepSkuModel->field('id')->where(['attribute_value_md5' => ['IN', join(',', $attributeSkus)]])->select();
                if (count($stepSkuData) > 0) {
                    $tmpStepSkuIds = array_column($stepSkuData, 'id');
                    unset($stepSkuData);
                    $stepSkuIds .= join(',', $tmpStepSkuIds);
                }
            }
        }

        return $stepSkuIds;
    }


    /**
     * 处理工序分类所有节点所属根节点模块熟悉
     * @param $stepCategoryDict
     * @param $treeData
     * @param $stepCategoryBelongModuleDict
     * @param array $parentData
     */
    protected function recursiveStepCategoryBelongModuleDict(&$stepCategoryDict, $treeData, $stepCategoryBelongModuleDict, $parentData = [])
    {
        foreach ($treeData as $treeItem) {
            if ((int)$treeItem['parent_id'] === 0) {
                $parentData = [
                    'id' => $treeItem['id'],
                    'code' => $treeItem['code'],
                    'entity_type' => array_key_exists($treeItem['id'], $stepCategoryBelongModuleDict) ? $stepCategoryBelongModuleDict[$treeItem['id']]['module_code'] : '',
                ];

                if (!array_key_exists($treeItem['id'], $stepCategoryDict)) {
                    $stepCategoryDict[$treeItem['id']] = $parentData;
                }
            } else {
                if (!array_key_exists($treeItem['id'], $stepCategoryDict)) {
                    $stepCategoryDict[$treeItem['id']] = [
                        'id' => $treeItem['id'],
                        'code' => $treeItem['code'],
                        'entity_type' => $parentData['entity_type'],
                    ];
                }
            }

            if (!empty($treeItem['children'])) {
                $this->recursiveStepCategoryBelongModuleDict($stepCategoryDict, $treeItem['children'], $stepCategoryBelongModuleDict, $parentData);
            }
        }
    }


    /**
     * 获取所有工序的Upstream配置
     * @param $projectTemplateId
     * @param $projectId
     * @return array
     * @throws Exception
     */
    public function getAllUpstream($projectTemplateId, $projectId)
    {
        // 1. 获取所有工序列表
        $stepModel = model(StepModel::class);
        $stepListData = $stepModel->field('id,name,code,step_category_id,engine,batch')->select();
        $stepListDataDict = array_column($stepListData, null, 'id');

        // 2. 获取 step_category 根节点所属实体code
        $stepCategoryModel = model(StepCategoryModel::class);
        $stepCategoryBelongModuleData = $stepCategoryModel->alias('category')
            ->join("module ON module.id = category.entity_id", 'LEFT')
            ->where([
                'category.parent_id' => 0
            ])
            ->field('category.id,category.code,module.code as module_code')
            ->select();
        $stepCategoryBelongModuleDict = array_column($stepCategoryBelongModuleData, null, 'id');

        // 3. 查询当前工序的 step_category_code 和 entity_type
        $stepCategoryListData = $stepCategoryModel->field('id,code,entity_id,parent_id')->select();
        $tree = new Tree('id', 'parent_id');
        $tree->load($stepCategoryListData);
        $treeData = $tree->DeepTree();
        $stepCategoryDict = [];
        $this->recursiveStepCategoryBelongModuleDict($stepCategoryDict, $treeData, $stepCategoryBelongModuleDict);

        // 4. 查询工序上游数据
        $stepUpstreamModel = model(StepUpstreamModel::class);
        $where = [];
        if (!empty($projectTemplateId)) {
            $where = ['filter' => ['project_template_id' => $projectTemplateId, 'project_id' => 0]];
        }
        if (!empty($projectId)) {
            // project_id 如果是外包项目要转换到甲方项目id查询
            $projectData = model(ProjectModel::class)->field('type,belong_project_id')->where(['id' => $projectId])->find();
            if (!empty($projectData) && $projectData['type'] == 'outsource') {
                $projectId = $projectData['belong_project_id'];
            }
            $where = ['filter' => ['project_id' => $projectId]];
        }
        $stepUpstreamData = $stepUpstreamModel->selectData($where);
        $stepUpstreamDict = [];
        foreach ($stepUpstreamData['rows'] as $stepUpstreamItem) {
            $stepUpstreamDict[$stepUpstreamItem['step_id']][] = $stepUpstreamItem;
        }

        $stepUpstream = [];
        foreach ($stepListData as $stepListItem) {
            $upstream = [];
            if (array_key_exists($stepListItem['id'], $stepUpstreamDict)) {
                foreach ($stepUpstreamDict[$stepListItem['id']] as $stepUpstreamItem) {
                    $upstreamStepId = $stepUpstreamItem['config']['step_id'] ?? $stepUpstreamItem['upstream_step_id'];
                    if (!empty($stepListDataDict[$upstreamStepId])) {
                        $belongStepData = $stepListDataDict[$upstreamStepId];
                    } else {
                        $belongStepData = [
                            'step_category_id' => 0,
                            'code' => '',
                            'name' => '',
                        ];
                    }
                    if ($stepUpstreamItem['relation'] == 'start') {
                        $initialization = 'yes';
                    } else {
                        $initialization = $stepUpstreamItem['initialization'];
                    }
                    if ($stepUpstreamItem['type'] = 'step' && isset($stepCategoryDict[$belongStepData['step_category_id']])) {
                        $upstream[] = [
                            'type' => $stepUpstreamItem['type'],
                            "step_category_code" => $stepCategoryDict[$belongStepData['step_category_id']]['code'],
                            "step_code" => $belongStepData['code'],
                            "step_name" => $belongStepData['name'],
                            "entity_type" => $stepCategoryDict[$belongStepData['step_category_id']]['entity_type'],
                            'initialization' => $initialization, //  $stepUpstreamItem['initialization']
                            'relevance' => $stepUpstreamItem['relevance'],
                            'relation' => $stepUpstreamItem['relation'],
                            'relation_type' => $stepUpstreamItem['relation_type'],
                        ];
                    } else {
                        $upstream[] = [
                            'type' => $stepUpstreamItem['type'],
                            "step_category_code" => '',
                            "step_code" => $belongStepData['code'],
                            "step_name" => $belongStepData['name'],
                            "config" => $stepUpstreamItem['config'],
                            "entity_type" => '',
                            'initialization' => $initialization, // $stepUpstreamItem['initialization']
                            'relevance' => $stepUpstreamItem['relevance'],
                            'relation' => $stepUpstreamItem['relation'],
                            'relation_type' => $stepUpstreamItem['relation_type'],
                        ];
                    }
                }
            } else {
                $upstream[] = [
                    "step_category_code" => "",
                    "step_code" => "",
                    "step_name" => "",
                    "entity_type" => "",
                    'initialization' => "no",
                    'relevance' => "no",
                    'relation' => '',
                    'relation_type' => '',
                ];
            }

            $stepUpstream[$stepListItem['code']] = [
                "upstream" => $upstream,
                'engine' => $stepListItem['engine'],
                'batch' => $stepListItem['batch']
            ];
        }

        return $stepUpstream;
    }


    /**
     * 获取指定工序的Upstream配置
     * @param $stepId
     * @param $projectTemplateId
     * @param $projectId
     * @param int $stepWorkflowId
     * @return array
     */
    public function getStepUpstream($stepId, $projectTemplateId, $projectId, int $stepWorkflowId = 0): array
    {
        $stepUpstreamWhere = [];

        if (!empty($projectId)) {
            $stepUpstreamWhere = [
                'project_id' => $projectId,
                'project_template_id' => 0
            ];
        }
        if (!empty($projectTemplateId)) {
            $stepUpstreamWhere = [
                'project_id' => 0,
                'project_template_id' => $projectTemplateId
            ];
        }
        if (!empty($stepWorkflowId)) {
            $stepUpstreamWhere['step_workflow_id'] = $stepWorkflowId;
        }

        $stepUpstreamWhere['step_id'] = $stepId;
        $stepUpstreamData = model(StepUpstreamModel::class)
            ->where($stepUpstreamWhere)
            ->select();

        if (empty($projectTemplateId) && empty($stepUpstreamData)) {
            // 兼容老工序流查询，项目upstream没有取平台的
            $stepUpstreamWhere['project_id'] = 0;
            $stepUpstreamData = model(StepUpstreamModel::class)
                ->where($stepUpstreamWhere)
                ->select();
        }

        $stepUpstreamList = [];
        if (!empty($stepUpstreamData)) {
            $stepFilter = [];
            foreach ($stepUpstreamData as $stepUpstreamItem) {

                if (!empty($stepUpstreamItem['step_workflow_id'])) {
                    if (!in_array($stepUpstreamItem['upstream_step_id'], $stepFilter)) {
                        $stepFilter[] = $stepUpstreamItem['upstream_step_id'];
                    }
                    $relevance = 'no';
                    if ($stepUpstreamItem['relation'] != 'start') {
                        $relevance = 'yes';
                    }
                    $stepUpstreamList[] = [
                        "id" => $stepUpstreamItem['id'],
                        "step_id" => $stepUpstreamItem['upstream_step_id'],
                        "initialization" => $stepUpstreamItem['initialization'],
                        "relevance" => $relevance,
                        "relation" => $stepUpstreamItem['relation'] ?? '',// 上游关系
                        "relation_type" => $stepUpstreamItem['relation_type'] ?? '', // 关联类型
                    ];
                } else {
                    $stepUpstreamItem['config'] = json_decode($stepUpstreamItem['config'], true);
                    if (!in_array($stepUpstreamItem['config']['step_id'], $stepFilter)) {
                        $stepFilter[] = $stepUpstreamItem['config']['step_id'];
                    }
                    $stepUpstreamList[] = [
                        "id" => $stepUpstreamItem['id'],
                        "step_id" => $stepUpstreamItem['config']['step_id'],
                        "initialization" => $stepUpstreamItem['initialization'],
                        "relevance" => $stepUpstreamItem['relevance'],
                        "relation" => $stepUpstreamItem['relation'] ?? '',// 上游关系
                        "relation_type" => $stepUpstreamItem['relation_type'] ?? '', // 关联类型
                    ];
                }
            }

            $stepModel = model(StepModel::class);
            // 暂时去掉查询缓存 ->cache(C('database.default_query_cache_time'))
            $stepData = $stepModel->field('id,code,name')->where(['id' => ['IN', join(',', $stepFilter)]])->select();
            $stepDict = array_column($stepData, null, 'id');

            foreach ($stepUpstreamList as &$item) {
                // 关联的工序找不到    continue 允许前端修正数据
                if (!array_key_exists($item['step_id'], $stepDict)) {
//                    unset($stepUpstreamList[$key]);
                    continue;
                }
                $item['step_name'] = $stepDict[$item['step_id']]['name'];
                $item['step_code'] = $stepDict[$item['step_id']]['code'];
            }
        }

        return array_values($stepUpstreamList);
    }

    /**
     * 移除工序上游查询缓存
     * @param int $stepId
     * @param int $projectTemplateId
     * @return void
     */
    public function removeStepUpstreamQueryCache(int $stepId, int $projectTemplateId)
    {
        $ck = "getStepUpstream;projectTemplate:$projectTemplateId;Step:$stepId";
        Cache::rm($ck);
    }

    /**
     * 获取指定工序的Downstream配置
     * @param $stepId
     * @return array
     */
    public function getStepDownstream($stepId): array
    {
        $stepUpstreamModel = model(StepUpstreamModel::class);
        $stepDownstreamData = $stepUpstreamModel
            ->where(['upstream_step_id' => $stepId])
            ->where(['type' => 'step'])
            ->field("step_id")
            ->group("step_id")
            ->select();

        if (!empty($stepDownstreamData)) {
            $stepDownstreamStepIdList = array_column($stepDownstreamData, 'step_id', 'step_id');
            $stepData = $this->stepModel->field('id,code,name')->where(['id' => ['IN', join(',', $stepDownstreamStepIdList)]])->select();
            $stepDict = array_column($stepData, null, 'id');

            foreach ($stepDownstreamData as $key => &$item) {
                if (!array_key_exists($item['step_id'], $stepDict)) {
                    unset($stepDownstreamData[$key]);
                    continue;
                }
                $item['step_name'] = $stepDict[$item['step_id']]['name'];
                $item['step_code'] = $stepDict[$item['step_id']]['code'];
            }
            return array_values($stepDownstreamData);
        }

        return $stepDownstreamData;
    }

    /**
     * 获取所有工序的Upstream配置
     * @return array
     */
    public function getAllUpstreamOutside()
    {
        // 1. 获取所有工序列表
        $stepModel = model(StepModel::class);
        $stepListData = $stepModel->field('id,name,code,step_category_id,engine,batch')->select();
        $stepListDataDict = array_column($stepListData, null, 'id');

        // 2. 获取 step_category 根节点所属实体code
        $stepCategoryModel = model(StepCategoryModel::class);
        $stepCategoryBelongModuleData = $stepCategoryModel->alias('category')
            ->join("module ON module.id = category.entity_id", 'LEFT')
            ->where([
                'category.parent_id' => 0
            ])
            ->field('category.id,category.code,module.code as module_code')
            ->select();
        $stepCategoryBelongModuleDict = array_column($stepCategoryBelongModuleData, null, 'id');

        // 3. 查询当前工序的 step_category_code 和 entity_type
        $stepCategoryListData = $stepCategoryModel->field('id,code,entity_id,parent_id')->select();
        $tree = new Tree('id', 'parent_id');
        $tree->load($stepCategoryListData);
        $treeData = $tree->DeepTree();
        $stepCategoryDict = [];
        $this->recursiveStepCategoryBelongModuleDict($stepCategoryDict, $treeData, $stepCategoryBelongModuleDict);

        // 4. 查询工序上游数据
        $stepUpstreamOutsideModel = model(StepUpstreamOutsideModel::class);
        $stepUpstreamData = $stepUpstreamOutsideModel->selectData();
        $stepUpstreamDict = [];
        foreach ($stepUpstreamData['rows'] as $stepUpstreamItem) {
            $stepUpstreamDict[$stepUpstreamItem['step_id']][] = $stepUpstreamItem;
        }

        $stepUpstream = [];
        foreach ($stepListData as $stepListItem) {
            $upstream = [];
            if (array_key_exists($stepListItem['id'], $stepUpstreamDict)) {
                foreach ($stepUpstreamDict[$stepListItem['id']] as $stepUpstreamItem) {
                    if (!empty($stepListDataDict[$stepUpstreamItem['config']['step_id']])) {
                        $belongStepData = $stepListDataDict[$stepUpstreamItem['config']['step_id']];
                    } else {
                        $belongStepData = [
                            'step_category_id' => 0,
                            'code' => '',
                            'name' => '',
                        ];
                    }
                    if ($stepUpstreamItem['type'] = 'step' && isset($stepCategoryDict[$belongStepData['step_category_id']])) {
                        $upstream[] = [
                            'type' => $stepUpstreamItem['type'],
                            "step_category_code" => $stepCategoryDict[$belongStepData['step_category_id']]['code'],
                            "step_code" => $belongStepData['code'],
                            "step_name" => $belongStepData['name'],
                            "entity_type" => $stepCategoryDict[$belongStepData['step_category_id']]['entity_type'],
                        ];
                    } else {
                        $upstream[] = [
                            'type' => $stepUpstreamItem['type'],
                            "step_category_code" => '',
                            "step_code" => $belongStepData['code'],
                            "step_name" => $belongStepData['name'],
                            "config" => $stepUpstreamItem['config'],
                            "entity_type" => '',
                        ];
                    }
                }
            } else {
                $upstream[] = [
                    "step_category_code" => "",
                    "step_code" => "",
                    "step_name" => "",
                    "entity_type" => ""
                ];
            }

            $stepUpstream[$stepListItem['code']] = [
                "upstream" => $upstream,
                'engine' => $stepListItem['engine'],
                'batch' => $stepListItem['batch']
            ];
        }

        return $stepUpstream;
    }

    /**
     * 获取指定工序的Upstream配置
     * @param $stepId
     * @return array
     * @throws Exception
     */
    public function getStepUpstreamOutside($stepId)
    {
        $stepUpstreamOutsideModel = model(StepUpstreamOutsideModel::class);
        $stepUpstreamData = $stepUpstreamOutsideModel->selectData(['filter' => ['step_id' => $stepId]]);

        $stepUpstreamList = [];
        if (!empty($stepUpstreamData['rows'])) {
            $stepFilter = [];
            foreach ($stepUpstreamData['rows'] as $stepUpstreamItem) {
                if ($stepUpstreamItem['type'] === 'step') {
                    if (!in_array($stepUpstreamItem['config']['step_id'], $stepFilter)) {
                        $stepFilter[] = $stepUpstreamItem['config']['step_id'];
                    }

                    $stepUpstreamList[] = [
                        "id" => $stepUpstreamItem['id'],
                        "step_id" => $stepUpstreamItem['config']['step_id']
                    ];
                }
            }

            $stepModel = model(StepModel::class);
            $stepData = $stepModel->field('id,code,name')->where(['id' => ['IN', join(',', $stepFilter)]])->select();
            $stepDict = array_column($stepData, null, 'id');

            foreach ($stepUpstreamList as $key => &$item) {
                // 关联的工序找不到    continue 允许前端修正数据
                if (!array_key_exists($item['step_id'], $stepDict)) {
//                    unset($stepUpstreamList[$key]);
                    continue;
                }
                $item['step_name'] = $stepDict[$item['step_id']]['name'];
                $item['step_code'] = $stepDict[$item['step_id']]['code'];
            }
        }

        return array_values($stepUpstreamList);
    }

    /**
     * 获取指定工序的Downstream配置
     * @param $stepId
     * @return array
     */
    public function getStepDownstreamOutside($stepId): array
    {
        $stepUpstreamOutsideModel = model(StepUpstreamOutsideModel::class);
        $stepDownstreamData = $stepUpstreamOutsideModel
            ->where("JSON_UNQUOTE(JSON_EXTRACT(config,'$.step_id')) = {$stepId} AND type = 'step'")
            ->field("step_id")
            ->group("step_id")
            ->select();

        if (!empty($stepDownstreamData)) {
            $stepDownstreamStepIdList = array_column($stepDownstreamData, 'step_id', 'step_id');
            $stepData = $this->stepModel->field('id,code,name')->where(['id' => ['IN', join(',', $stepDownstreamStepIdList)]])->select();
            $stepDict = array_column($stepData, null, 'id');

            foreach ($stepDownstreamData as $key => &$item) {
                if (!array_key_exists($item['step_id'], $stepDict)) {
                    unset($stepDownstreamData[$key]);
                    continue;
                }
                $item['step_name'] = $stepDict[$item['step_id']]['name'];
                $item['step_code'] = $stepDict[$item['step_id']]['code'];
            }
            $stepDownstreamData = array_values($stepDownstreamData);
        }

        return $stepDownstreamData;
    }

    /**
     * 异步去执行sku生成
     * @param $stepId
     */
    public function generateStepSkuByStepIdAsync($stepId)
    {
        $this->appendStepSkuToQueue($stepId, 0);
    }


    /**
     * 根据前置工序配置，组织工序列表（打平）
     * @param        $projectId
     * @param bool $relative 关联性
     * @param string $direction 方向 up上游 / down下游
     * @param int $searchDepth 搜索深度 默认1 [lt 0] 全部
     * @return array
     */
    public function getStepRelationMapV1($projectId, $relative = true, $direction = 'down', $searchDepth = 1, $maxDepth = 20)
    {
        $project = model(ProjectModel::class)->find($projectId);
        $stepUpstreamModel = model(StepUpstreamModel::class);
        $filter = [
            "type" => 'step',
            "project_template_id" => $project["project_template_id"],
        ];
        if ($relative === false) {
            $filter[] = [
                'relevance' => 'no',
            ];
        }
        $stepDownstreamData = $stepUpstreamModel
            ->where($filter)
            ->field("step_id,config")
            ->select();
        $stepCategorySingleTree = [];
        // 按方向，组织单层关系树
        while (!empty($stepDownstreamData)) {
            $relationData = array_shift($stepDownstreamData);
            $relationData['config'] = json_decode($relationData['config'], true);
            switch ($direction) {
                case 'up':
                    $currentNode = $relationData['step_id'];
                    $nextNode = $relationData['config']['step_id'] ?? 0;
                    break;
                case 'down':
                default:
                    $currentNode = $relationData['config']['step_id'] ?? 0;
                    $nextNode = $relationData['step_id'];
            }
            if (empty($currentNode) || empty($nextNode)) {
                continue;
            }
            if (!isset($stepCategorySingleTree[$currentNode])) {
                $stepCategorySingleTree[$currentNode] = [];
            }
            $stepCategorySingleTree[$currentNode][] = $nextNode;
        }
        $res = [];
        // bfs 组织工序的多级上游/下游工序，打平到工序map
        $nodeQueue = array_map(fn($node) => [
            'parent_id' => 0,
            'path' => [],
            'id' => $node,
            'depth' => 0,
        ], array_keys($stepCategorySingleTree));
        while (!empty($nodeQueue)) {
            $currentNode = array_shift($nodeQueue);
            foreach ($stepCategorySingleTree[$currentNode['id']] ?? [] as $child) {
                $parentId = $currentNode['parent_id'] > 0 ? $currentNode['parent_id'] : $currentNode['id'];
                if (!isset($res[$parentId])) {
                    $res[$parentId] = [];
                }
                if (!in_array($child, $res[$parentId])) {
                    $res[$parentId][$child] = $child;
                    $nextDepth = $currentNode['depth'] + 1;
                    if (($searchDepth < 0 && $nextDepth < $maxDepth) || $nextDepth < $searchDepth) {
                        $nodeQueue[] = [
                            'parent_id' => $parentId,
                            'id' => $child,
                            'depth' => $nextDepth,
                        ];
                    }
                }
            }
        }
        return $res;
    }

    /**
     * 获取所有工序的Downstream配置
     * @param $projectId
     * @param bool $relative
     * @return array
     */
    public function getAllStepDownstreamMap($projectId, int $taskWorkFlowId = 0, bool $relative = true): array
    {
        $res = [];
        $project = model(ProjectModel::class)->field('id,project_template_id')->find($projectId);
        if (empty($project)) {
            return $res;
        }
        $fields = 'step_id,upstream_step_id,relevance,relation_type';
        if (!empty($taskWorkFlowId)) {
            $stepUpstreamFilter = [
                'step_upstream.type' => 'step',
                [
                    'step_upstream.project_id' => $project['id'],
                    [
                        'step_upstream.project_template_id' => 0,
                        'step_upstream.project_id' => 0,
                    ],
                    '_logic' => 'or',
                ],
                'step_workflow.is_enable' => 'yes',
            ];
            if (!$relative) {
                $stepUpstreamFilter['relation_type'] = TaskRelationModel::RELATION_TYPE_ENTITY;
            }
        } else {
            // 老工序流
            $stepUpstreamFilter = [
                'step_upstream.type' => 'step',
                [
                    'step_upstream.project_template_id' => $project['project_template_id'],
                    'step_upstream.project_id' => 0,
                ],
            ];
            if (!$relative) {
                $stepUpstreamFilter['step_upstream.relevance'] = 'no';
            }
        }
        $stepDownstreamData = model(StepUpstreamModel::class)
            ->join('step_workflow on step_workflow.id = step_upstream.step_workflow_id', 'left')
            ->where($stepUpstreamFilter)
            ->order('step_upstream.project_id desc')
            ->group('code, step_id, upstream_step_id')
            ->field($fields)
            ->select();
        if (empty($stepDownstreamData)) {
            return $res;
        }
        $stepData = $this->stepModel->field('id,code,name')->select();
        $stepDict = array_column($stepData, null, 'id');
        foreach ($stepDownstreamData as $key => &$item) {
            if (!array_key_exists($item['step_id'], $stepDict) || !array_key_exists($item['upstream_step_id'], $stepDict)) {
                unset($stepDownstreamData[$key]);
                continue;
            }
            $item['step_name'] = $stepDict[$item['step_id']]['name'];
            $item['step_code'] = $stepDict[$item['step_id']]['code'];
            $res[$item['upstream_step_id']][$item['step_id']] = $item['step_id'];
        }
        return $res;
    }

    /**
     * 合并模块二级分类配置
     * @return array|mixed
     */
    public function getMergeModuleStepCategoryConfig()
    {
        return OptionsService::getInstance()->getOptionsData('assign_task_list_step_category_filter_config');
    }

    /**
     * 合并模块 所包含的二级分类查询
     * @param $moduleCode
     * @param bool $includeDeprecate 是否包含弃用
     * @return array
     */
    public function getStepCategoryByMergeModuleConfig($moduleCode, $includeDeprecate = true)
    {
        $stepCategoryIds = OptionsService::getInstance()->getOptionsConfigItemData('assign_task_list_step_category_filter_config', $moduleCode);
        if (empty($stepCategoryIds)) {
            return [];
        }
        $filter = ['id' => ['IN', $stepCategoryIds]];
        if ($includeDeprecate === false) {
            $filter['is_deprecate'] = 'no';
        }
        return $this->stepCategoryModel->where($filter)->order('index ASC')->select();
    }

    /**
     * 查询显示的工序分类信息
     * @param bool $includeDeprecate
     * @return array|false|mixed|string
     */
    public function getShowStepCategory($includeDeprecate = true)
    {
        $config = $this->getMergeModuleStepCategoryConfig();
        $ids = [];
        foreach ($config as $stepCategoryIds) {
            foreach (explode(',', $stepCategoryIds) as $stepCategoryId) {
                $ids[$stepCategoryId] = $stepCategoryId;
            }
        }
        $filter = ['id' => ['IN', array_values($ids)]];
        if ($includeDeprecate === false) {
            $filter['is_deprecate'] = 'no';
        }
        return $this->stepCategoryModel->where($filter)->order('index ASC')->select();
    }

    /**
     * 前置配置工序列表
     * @return array
     */
    public function getProjectTemplateStepCategoryList()
    {
        // 获取项目模板
        $projectTemplateModel = model(ProjectTemplateModel::class);
        $projectList = $projectTemplateModel->field('id,name')->select();
        if (empty($projectList)) {
            return [];
        }

        // 获取工序列表
        $getStepCategoryList = $this->getStepCategoryList();
        if (empty($getStepCategoryList)) {
            return [];
        }

        // 把项目模板作为顶级目录
        $returnData = [];
        foreach ($projectList as $directoryListValue) {
            $getStepCategoryList = array_map(function ($tmp) use ($directoryListValue) {
                $tmp['parent_id'] = $directoryListValue['id'];
                return $tmp;
            }, $getStepCategoryList);
            $returnData[] = [
                'id' => $directoryListValue['id'],
                'name' => $directoryListValue['name'],
                'code' => '',
                'parent_id' => '0',
                'entity_id' => '0',
                'is_standard' => 'yes',
                'task_type' => 'project_template',
                'is_deprecate' => 'no',
                'children' => $getStepCategoryList,
            ];
        }
        return $returnData;
    }

    /**
     * 前置工序-工序详情
     * @param $param
     * @param $projectTemplateId
     * @return array
     */
    public function projectTemplateStepList($param, $projectTemplateId)
    {
        $fields = $param['fields'] ?? '';
        $filter = $param['filter'] ?? [];
        $page = $param['page'] ?? [0, C("database.database_max_select_rows")];
        $keySep = '__';
        $joinFields = build_complex_query_fields(explode(',', $fields), $keySep);

        $stepList = model(StepModel::class)
            ->join('step_category on step_category.id = step.step_category_id')
            ->field(join(',', $joinFields))
            ->where($filter)
            ->page($page[0], $page[1])
            ->select();
        if (empty($stepList)) {
            return [];
        }
        return $this->getFrontStepTag($stepList, $projectTemplateId, $keySep);
    }

    /**
     * 获取前置工序签
     * @param $stepList
     * @param $projectTemplateId
     * @param $keySep
     * @return mixed
     */
    public function getFrontStepTag($stepList, $projectTemplateId, $keySep)
    {
        // 查询项目模板已经配置的工序流
        $stepWorkFlowList = model(StepWorkflowModel::class)
            ->where(['project_template_id' => $projectTemplateId])
            ->where("JSON_UNQUOTE(JSON_EXTRACT(config,'$.stepStr')) != ''")
            ->select();

        // 已使用的工序流
        $useStepArr = $this->getUseStepUpstream($stepWorkFlowList);
        // 查询没有被其他工序设置为前置工序
        $configStepIds = $this->getConfigStepUpstream($projectTemplateId);

        // 处理tag
        foreach ($stepList as &$row) {
            $tag = [];
            if (!empty($keySep)) {
                $row = separate_complex_query_data($row, $keySep);
            }
            // 工序流没有配置
            if (!empty($useStepArr) && !in_array($row['step']['id'], $useStepArr)) {
                // 工序流配置了但是没使用
                $tag = array_merge($tag, ['not_use']);
            }
            // 工序流未被设置
            if (!empty($configStepIds) && !in_array($row['step']['id'], $configStepIds)) {
                $tag = array_merge($tag, ['not_configured']);
            }
            $row['tag'] = $tag;
            unset($tag);
        }
        return $stepList;
    }

    /**
     * 获取已使用的工序流
     * @param $stepWorkFlowData
     * @return array|false|string[]
     */
    public function getUseStepUpstream($stepWorkFlowData)
    {
        if (empty($stepWorkFlowData)) {
            return [];
        }
        $stepWorkFlowData = array_map(function ($tmp) {
            $config = json_decode($tmp['config'], true);
            $tmp['step_str'] = $config['stepStr'] ?? '';
            unset($tmp['config']);
            return $tmp;
        }, $stepWorkFlowData);
        return explode(',', trim(implode('', array_column($stepWorkFlowData, 'step_str')), ','));
    }

    /**
     * 获取已经被配置的工序流
     * @param $projectTemplateId
     * @return array
     */
    public function getConfigStepUpstream($projectTemplateId)
    {
        $stepUpstreamData = model(StepUpstreamModel::class)->alias('su')
            ->field('config,project_template_id')
            ->join("step on step.id = su.step_id")
            ->where(['project_template_id' => $projectTemplateId])
            ->where("config !=''")
            ->select();
        $stepUpstreamData = array_map(function ($tmp) {
            $tmp['config_step_id'] = json_decode($tmp['config'], true)['step_id'];
            return $tmp;
        }, $stepUpstreamData);
        return array_column($stepUpstreamData, 'config_step_id');
    }

//    /**
//     * 获取上游流程,及其工序
//     * @param $projectTemplateId
//     * @param $startStep
//     * @param bool $crossCurrentModule
//     * @param bool $crossShotAsset
//     * @return array
//     */
//    public function getUpstreamProcess($projectTemplateId, $startStep, $crossCurrentModule = true, $crossShotAsset = true)
//    {
//        $res = [
//            "step_up_list" => [],
//            "module_process" => []
//        ];
//        $stepUpstreamModel = model(StepUpstreamModel::class);
//        $stepDownstreamData = $stepUpstreamModel
//            ->where(["type" => 'step', "project_template_id" => $projectTemplateId])
//            ->field("step_id,JSON_UNQUOTE(JSON_EXTRACT(config,'$.step_id')) as parent_step_id")
//            ->select();
//
//        if (empty($stepDownstreamData)) {
//            return $res;
//        }
//        $stepData = $this->stepModel->field('id,code,name,step_category_id')->select();
//        $stepCategoryList = $this->stepCategoryModel->field("id,parent_id,entity_id")->select();
//        $stepCategoryList = array_column($stepCategoryList, null, 'id');
//        $stepModuleMap = [];
//        foreach ($stepData as $stepDataItem) {
//            if (empty($stepCategoryList[$stepDataItem['step_category_id']])) {
//                continue;
//            }
//            $stepModuleMap[$stepDataItem["id"]] = module_by_id($stepCategoryList[$stepCategoryList[$stepDataItem['step_category_id']]['parent_id']]['entity_id']);
//        }
//
//        foreach ($stepDownstreamData as $key => $stepDownstreamDataItem) {
//            if (!isset($stepModuleMap[$stepDownstreamDataItem["step_id"]]) || !isset($stepModuleMap[$stepDownstreamDataItem["parent_step_id"]])) {
//                unset($stepDownstreamData[$key]);
//                continue;
//            }
//            $stepDownstreamDataItem["parent_step_id"] = intval($stepDownstreamDataItem["parent_step_id"]);
//            $stepDownstreamDataItem["module"] = $stepModuleMap[$stepDownstreamDataItem["step_id"]];
//            $stepDownstreamDataItem["parent_module"] = $stepModuleMap[$stepDownstreamDataItem["parent_step_id"]];
//            $stepDownstreamData[$key] = $stepDownstreamDataItem;
//        }
//
//        $res = [];
//        $moduleProcess = [];
//        $crossShotAssetData = [
//            "is_cross" => $crossCurrentModule,
//            "is_cross_asset_shot" => $crossShotAsset,
//            "asset_code" => ['asset', 'design', 'level'],
//            "shot_code" => ['storyboard', 'sequence', 'shot']
//        ];
//        $stepUpList = [];
//        $this->getUpstreamRecursively($startStep, $res, $moduleProcess, $stepUpList, $stepDownstreamData, $crossShotAssetData);
//
//        $stepUpList = array_values($stepUpList);
//
//        return [
//            "step_up_list" => $stepUpList,
//            "module_process" => $moduleProcess
//        ];
//    }

    /**
     * 递归获取上游
     * @param $startStep
     * @param $res
     * @param $moduleProcess
     * @param $stepUpList
     * @param $stepDownstreamData
     * @param $crossShotAssetData
     */
    public function getUpstreamRecursively($startStep, &$res, &$moduleProcess, &$stepUpList, $stepDownstreamData, $crossShotAssetData)
    {
        $data = [];
        foreach ($stepDownstreamData as $stepDownstreamDataItem) {
            if ($stepDownstreamDataItem["step_id"] == $startStep) {
                //是否跨越当前模块
                if ($crossShotAssetData["is_cross"]) {
                    //不跨越镜头与资产
                    if (!$crossShotAssetData["is_cross_asset_shot"]) {
                        $isAddData = !(
                            (in_array($stepDownstreamDataItem["module"]["code"], $crossShotAssetData["asset_code"]) && in_array($stepDownstreamDataItem["parent_module"]["code"], $crossShotAssetData["shot_code"])) ||
                            (in_array($stepDownstreamDataItem["module"]["code"], $crossShotAssetData["shot_code"]) && in_array($stepDownstreamDataItem["parent_module"]["code"], $crossShotAssetData["asset_code"]))
                        );
                    } else {
                        $isAddData = true;
                    }
                } else {
                    $isAddData = $stepDownstreamDataItem["module"]["id"] == $stepDownstreamDataItem["parent_module"]["id"];
                }

                if ($isAddData) {
                    $data[] = $stepDownstreamDataItem;
                    $stepUpList[$stepDownstreamDataItem["parent_step_id"]] = $stepDownstreamDataItem["parent_step_id"];


                    // $moduleProcess 只有第一层会为空
                    if (empty($moduleProcess)) {
                        $moduleProcess = $stepDownstreamDataItem["module"];
                    }

                    //判定当前模块与下一模块是否相同
                    if ($stepDownstreamDataItem["parent_module"]["id"] != $moduleProcess['id']) {
                        if (!isset($moduleProcess['relation'][$stepDownstreamDataItem["parent_module"]["id"]])) {
                            $moduleProcess['relation'][$stepDownstreamDataItem["parent_module"]["id"]] = $stepDownstreamDataItem["parent_module"];
                        }
                    }
                }
            }
        }


        if (!empty($data)) {
            if (empty($res)) {
                $res = $data;
                foreach ($res as $index => $resItem) {
                    //如果不存在则传递自己
                    if (isset($moduleProcess["relation"][$resItem["parent_module"]["id"]])) {
                        $this->getUpstreamRecursively($resItem["parent_step_id"], $res[$index], $moduleProcess['relation'][$resItem["parent_module"]["id"]], $stepUpList, $stepDownstreamData, $crossShotAssetData);
                    } else {
                        $this->getUpstreamRecursively($resItem["parent_step_id"], $res[$index], $moduleProcess, $stepUpList, $stepDownstreamData, $crossShotAssetData);
                    }
                }
            } else {
                //判定module 是否相等,相等则排除
                $res["children"] = $data;
                foreach ($res["children"] as $index => $resItem) {
                    //如果不存在则传递自己
                    if (isset($moduleProcess["relation"][$resItem["parent_module"]["id"]])) {
                        $this->getUpstreamRecursively($resItem["parent_step_id"], $res["children"][$index], $moduleProcess['relation'][$resItem["parent_module"]["id"]], $stepUpList, $stepDownstreamData, $crossShotAssetData);
                    } else {
                        $this->getUpstreamRecursively($resItem["parent_step_id"], $res["children"][$index], $moduleProcess, $stepUpList, $stepDownstreamData, $crossShotAssetData);
                    }
                }
            }
        }
    }

    /**
     * 获取指定工序的Upstream配置
     * @param $stepIds
     * @param int $projectTemplateId
     * @return array
     * @throws Exception
     */
    public function batchGetStepUpstreamMap($stepIds, $projectTemplateId = 0)
    {
        $stepUpstreamData = model(StepUpstreamModel::class)->where(['step_id' => ["IN", $stepIds], 'project_template_id' => $projectTemplateId])->select();

        $stepUpstreamMapList = [];
        foreach ($stepUpstreamData as $stepUpstreamItem) {
            $stepUpstreamItem['config'] = json_decode($stepUpstreamItem['config'], true);
            if ($stepUpstreamItem['type'] === 'step') {
                $stepUpstreamMapList[$stepUpstreamItem["step_id"]][] = $stepUpstreamItem['config']['step_id'];
            }
        }

        return $stepUpstreamMapList;
    }


    /**
     * 获取上游map
     * @param $projectTemplateId
     * @param $projectId
     * @param $stepWorkflowId
     * @param $type
     * @param $crossCurrentModule
     * @param $crossShotAsset
     * @param $onlyRelativeNo
     * @param $isAddStepCategory
     * @return array|array[]
     */
    public function getUpstreamStepMap($projectTemplateId, $projectId, $stepWorkflowId, $type = 'up', $crossCurrentModule = true, $crossShotAsset = true, $onlyRelativeNo = false, $isAddStepCategory = false): array
    {
        $stepUpstreamModel = model(StepUpstreamModel::class);
        $stepUpstreamFilter = ["type" => 'step'];
        // 新工序流
        if (!empty($stepWorkflowId)) {
            $stepUpstreamFilter['step_workflow_id'] = ['GT', 0];
        }
        // 老工序流
        if (!empty($projectTemplateId) && empty($projectId)) {
            $stepUpstreamFilter['project_template_id'] = $projectTemplateId;
            $stepUpstreamFilter['project_id'] = 0;
            $stepUpstreamFilter['step_workflow_id'] = 0;
        }

        if ($onlyRelativeNo) {
            $stepUpstreamFilter['relevance'] = 'no';
        }
        $stepDownstreamData = $stepUpstreamModel
            ->where($stepUpstreamFilter)
            ->field("step_id,upstream_step_id as up_step_id")
            ->select();

        if (empty($stepDownstreamData)) {
            return [
                "step_downstream_map" => [],
                "module_downstream_map" => []
            ];
        }

        $stepData = StepService::getInstance()->getList([], 'id,code,name,step_category_id');
        $stepCategoryList = StepCategoryService::getInstance()->getList([], 'id,parent_id,entity_id');
        $stepCategoryList = array_column($stepCategoryList, null, 'id');
        $stepModuleCodeMap = [];
        $stepIdMap = [];
        foreach ($stepData as $stepDataItem) {
            $stepIdMap[$stepDataItem['id']] = $stepDataItem;
            if (empty($stepCategoryList[$stepDataItem['step_category_id']])) {
                continue;
            }
            if (empty($stepCategoryList[$stepCategoryList[$stepDataItem['step_category_id']]['parent_id']]['entity_id'])) {
                continue;
            }
            $stepModuleCodeMap[$stepDataItem["id"]] = module_by_id($stepCategoryList[$stepCategoryList[$stepDataItem['step_category_id']]['parent_id']]['entity_id'])['code'];
        }


        $stepDownstreamMap = [];
        $limitModuleCode = [['asset', 'design', 'level'], ['storyboard', 'sequence', 'shot']];

        //建立图结构
        foreach ($stepDownstreamData as $stepDownstreamDataItem) {
            if (empty($stepDownstreamDataItem['step_id']) || empty($stepDownstreamDataItem['up_step_id'])) {
                continue;
            }
            // 工序不存在
            if (
                !isset($stepIdMap[$stepDownstreamDataItem['step_id']])
                || !isset($stepIdMap[$stepDownstreamDataItem['up_step_id']])
            ) {
                continue;
            }
            // 无头工序排除
            if (!isset($stepModuleCodeMap[$stepDownstreamDataItem['step_id']])
                || !isset($stepModuleCodeMap[$stepDownstreamDataItem['up_step_id']])) {
                continue;
            }
            $downModuleCode = $stepModuleCodeMap[$stepDownstreamDataItem['step_id']];
            $upModuleCode = $stepModuleCodeMap[$stepDownstreamDataItem['up_step_id']];

            //相同模块不建立
            $moduleDownstreamMap = [];
            if ($downModuleCode != $upModuleCode) {
                //如果不跨越当前模块
                if (!$crossCurrentModule) {
                    continue;
                }
                //如果不跨越资产与镜头环节
                if (!$crossShotAsset && (
                        (in_array($downModuleCode, $limitModuleCode[0]) && in_array($upModuleCode, $limitModuleCode[1])) ||
                        (in_array($upModuleCode, $limitModuleCode[0]) && in_array($downModuleCode, $limitModuleCode[1])))
                ) {
                    continue;
                }

                if ($type == 'up') {
                    $moduleDownstreamMap = [
                        'up_module' => $upModuleCode,
                        'down_module' => $downModuleCode
                    ];
                } else {
                    $moduleDownstreamMap = [
                        'up_module' => $upModuleCode,
                        'down_module' => $downModuleCode,
                    ];
                }
            } else {
                $moduleDownstreamMap = [
                    'up_module' => $upModuleCode,
                    'down_module' => $upModuleCode,
                ];
            }

            if ($isAddStepCategory) {
                if ($type == 'up') {
                    $moduleDownstreamMap['step_category_id'] = $stepIdMap[$stepDownstreamDataItem['up_step_id']]['step_category_id'];
                } else {
                    $moduleDownstreamMap['step_category_id'] = $stepIdMap[$stepDownstreamDataItem['step_id']]['step_category_id'];
                }

            }


            if ($type == 'up') {
                $stepDownstreamMap[$stepDownstreamDataItem['step_id']][$stepDownstreamDataItem['up_step_id']] = $moduleDownstreamMap;
            } else {
                $stepDownstreamMap[$stepDownstreamDataItem['up_step_id']][$stepDownstreamDataItem['step_id']] = $moduleDownstreamMap;
            }
        }

        return $stepDownstreamMap;
    }

    /**
     * 获取开始工序下的所有工序(包含自己)
     * @param $startId
     * @param $stepIds
     * @param $moduleProcess
     * @param $stepDownstreamMap
     */
    public function getStartAllStepIds($startId, &$stepIds, &$moduleProcess, &$stepDownstreamMap)
    {
        if (isset($stepIds[$startId])) { //遍历过的不在遍历,防止循环递归
            return;
        }
        $stepIds[$startId] = $startId;
        if (isset($stepDownstreamMap[$startId])) {
            foreach ($stepDownstreamMap[$startId] as $stepId => $item) {
                if (isset($stepIds[$stepId])) { //遍历过的不在遍历,防止循环递归
                    continue;
                }
                if ($item['up_module'] !== $item['down_module']) {
                    $moduleProcess[$item['up_module']][$item['down_module']] = 1;
                }
                $this->getStartAllStepIds($stepId, $stepIds, $moduleProcess, $stepDownstreamMap);
            }
        }
    }


    /**
     * 获取上下游流程,及其工序
     * @param $projectTemplateId
     * @param $projectId
     * @param $stepWorkflowId
     * @param $startStep
     * @param $crossCurrentModule
     * @param $crossShotAsset
     * @param $type
     * @return array|array[]
     */
    public function getUpstreamProcess($projectTemplateId, $projectId, $stepWorkflowId, $startStep, $crossCurrentModule = true, $crossShotAsset = true, $type = 'up'): array
    {
        $upstreamStepMap = $this->getUpstreamStepMap($projectTemplateId, $projectId, $stepWorkflowId, $type, $crossCurrentModule, $crossShotAsset);

        if (empty($upstreamStepMap)) {
            return [
                "step_list" => [],
                "module_process" => []
            ];
        }
        $typeStepIds = [];
        $moduleProcess = [];
        $this->getStartAllStepIds($startStep, $typeStepIds, $moduleProcess, $upstreamStepMap);

        return [
            "step_list" => array_values($typeStepIds),
            "module_process" => $moduleProcess
        ];
    }

    /**
     * 获取上下游流程,及其工序
     * @param int $projectTemplateId 项目模板id
     * @param array $startStepIdList 开始工序idList
     * @param bool $crossCurrentModule 是否跨越当前模块
     * @param bool $crossShotAsset 是否跨越镜头资产
     * @param string $type 上游还是下游
     * @return array
     */
    public function getUpstreamProcessList($projectTemplateId, $startStepIdList, $crossCurrentModule = true, $crossShotAsset = true, $type = 'up', $onlyRelativeNo = false)
    {
        $res = [];
        $upstreamStepMap = $this->getUpstreamStepMap($projectTemplateId, 0, 0, $type, $crossCurrentModule, $crossShotAsset, $onlyRelativeNo);

        if (empty($upstreamStepMap)) {
            return $res;
        }
        foreach ($startStepIdList as $startStepId) {
            $typeStepIds = [];
            $moduleProcess = [];
            $this->getStartAllStepIds($startStepId, $typeStepIds, $moduleProcess, $upstreamStepMap);
            $res[$startStepId] = [
                "step_list" => array_values($typeStepIds),
                "module_process" => $moduleProcess
            ];
        }
        return $res;
    }

    /**
     * 批量查询任务上游(一层)
     * @param int $projectTemplateId 项目模板id
     * @param array $stepIdList 工序id list
     * @param bool $onlyRelativeNo 仅需要非关联性上游
     * @return array
     */
    public function getStepsUpstreams(int $projectTemplateId, array $stepIdList, bool $onlyRelativeNo = false)
    {
        $stepIds = implode(',', $stepIdList);
        $ck = "getStepUpstream;projectTemplate:$projectTemplateId;Step:$stepIds";
        $stepUpstreamFilter = ['step_id' => ['IN', $stepIds], 'project_template_id' => $projectTemplateId];
        if ($onlyRelativeNo) {
            $stepUpstreamFilter['relevance'] = 'no';
        }
        $stepUpstreamData = model(StepUpstreamModel::class)
            ->where($stepUpstreamFilter)
            ->cache($ck, C('database.default_query_cache_time'))
            ->select();
        $res = [];
        if (empty($stepUpstreamData)) {
            return $res;
        }
        $stepFilter = [];
        foreach ($stepUpstreamData as $stepUpstreamItem) {
            $stepUpstreamItem['config'] = json_decode($stepUpstreamItem['config'], true);
            if ($stepUpstreamItem['type'] !== 'step') {
                continue;
            }
            if (!in_array($stepUpstreamItem['config']['step_id'], $stepFilter)) {
                $stepFilter[] = $stepUpstreamItem['config']['step_id'];
            }
            $res[$stepUpstreamItem['step_id']][] = [
                "id" => $stepUpstreamItem['id'],
                'downstream_step_id' => $stepUpstreamItem['step_id'],
                "step_id" => $stepUpstreamItem['config']['step_id'],
                "initialization" => $stepUpstreamItem['initialization'],
                "relevance" => $stepUpstreamItem['relevance'],
            ];
        }

        $stepData = $this->getList(['id' => ['IN', join(',', $stepFilter)]], '*');
        $stepDict = array_column($stepData, null, 'id');
        foreach ($res as &$stepUpstreamList) {
            $t = [];
            foreach ($stepUpstreamList as $item) {
                // 关联的工序找不到    continue 允许前端修正数据
                if (!array_key_exists($item['step_id'], $stepDict)) {
                    continue;
                }
                $item['step_name'] = $stepDict[$item['step_id']]['name'];
                $item['step_code'] = $stepDict[$item['step_id']]['code'];
                $t[] = $item;
            }
            $stepUpstreamList = $t;
        }
        return $res;
    }

    /**
     * 获取开始工序下的所有工序(包含自己)
     * @param $type
     * @param $currentStepId
     * @param $currentModuleCode
     * @param $stepIds
     * @param $moduleProcess
     * @param $stepDownstreamMap
     */
    public function getStartAllStepWithModule($type, $currentStepId, $currentModuleCode, &$stepIds, &$moduleProcess, &$stepDownstreamMap)
    {
        $stepIds[$currentStepId] = ['step_id' => $currentStepId, 'module_code' => $currentModuleCode];
        if (isset($stepDownstreamMap[$currentStepId])) {
            foreach ($stepDownstreamMap[$currentStepId] as $stepId => $item) {
                if (isset($stepIds[$stepId])) { //遍历过的不在遍历,防止循环递归
                    return;
                }
                if ($item['up_module'] !== $item['down_module']) {
                    $moduleProcess[$item['up_module']][$item['down_module']] = 1;
                }
                $this->getStartAllStepWithModule($type, $stepId, $item[$type], $stepIds, $moduleProcess, $stepDownstreamMap);
            }
        }
    }

    /**
     * 批量获取下游/下游工序信息
     * @param int $projectTemplateId
     * @param array $startStepIdList
     * @param string $moduleCode
     * @param bool $crossCurrentModule
     * @param bool $crossShotAsset
     * @param string $type
     * @return array|array[]
     */
    public function getUpstreamProcessBatch(int $projectTemplateId, array $startStepIdList, string $moduleCode, bool $crossCurrentModule = true, bool $crossShotAsset = true, string $type = 'up')
    {
        $upstreamStepMap = $this->getUpstreamStepMap($projectTemplateId, 0, 0, $type, $crossCurrentModule, $crossShotAsset);
        $res = [];
        if (empty($upstreamStepMap)) {
            return $res;
        }
        foreach ($startStepIdList as $startStepId) {
            $typeStepIds = [];
            $moduleProcess = [];
            $this->getStartAllStepWithModule($type . '_module', $startStepId, $moduleCode, $typeStepIds, $moduleProcess, $upstreamStepMap);

            $res[$startStepId] = [
                'step_list' => $typeStepIds,
                'module_process' => $moduleProcess,
            ];
        }
        return $res;
    }


    /**
     * 获取上游环节
     * @return array
     */
    public function getUpstreamStepCategory($projectId, $stepId)
    {
        $project = model(ProjectModel::class)->field("id,project_template_id")->find($projectId);
        $projectTemplateId = $project['project_template_id'];
        $stepService = StepService::getInstance();
        $upstreamStepMap = $stepService->getUpstreamStepMap($projectTemplateId, 0, 0, 'up', true, true, false, true);
        $stepData = StepService::getInstance()->getList([], 'id,code,name,step_category_id');
        $stepData = array_column($stepData, null, 'id');

        $stepIds = [];
        $stepCategoryIds = [$stepData[$stepId]['step_category_id'] => $stepData[$stepId]['step_category_id']];
        $this->getStepUpstreamAllStepCategoryIds($stepId, $stepIds, $stepCategoryIds, $upstreamStepMap);
        $stepCategoryList = StepCategoryService::getInstance()->getList([], 'id,name,parent_id,entity_id');
        $stepCategoryList = array_column($stepCategoryList, null, 'id');

        $result = [];
        foreach ($stepCategoryIds as $stepCategoryId) {
//            if (empty($stepCategoryList[$stepCategoryList[$stepCategoryId]['parent_id']])) {
//                continue;
//            }
            $result[] = $stepCategoryList[$stepCategoryId];
//            $parentStepCategory = $stepCategoryList[$stepCategoryList[$stepCategoryId]['parent_id']];
//            $currentModule = module_by_id($parentStepCategory['entity_id'])['code'];
//            $stepModuleId = $stepCategoryList[$stepCategoryList[$stepData[$stepId]['step_category_id']]['parent_id']]['entity_id'];
//
//            if ($stepModuleId == $parentStepCategory['entity_id']) {
//                $result['current_module'][$currentModule][] = $currentStepCategory;
//            } else {
//                $result['other_module'][$currentModule][] = $currentStepCategory;
//            }
        }


        return $result;
    }

    /**
     * 获取开始工序下的所有环节(包含自己)
     * @return void
     */
    public function getStepUpstreamAllStepCategoryIds($startId, &$stepIds, &$stepCategoryIds, &$stepDownstreamMap)
    {
        if (isset($stepIds[$startId])) { //遍历过的不在遍历,防止循环递归
            return;
        }
        $stepIds[$startId] = $startId;
        if (isset($stepDownstreamMap[$startId])) {
            foreach ($stepDownstreamMap[$startId] as $stepId => $item) {
                $stepCategoryIds[$item['step_category_id']] = $item['step_category_id'];
                if (isset($stepIds[$stepId])) { //遍历过的不在遍历,防止循环递归
                    continue;
                }
                $this->getStepUpstreamAllStepCategoryIds($stepId, $stepIds, $stepCategoryIds, $stepDownstreamMap);
            }
        }
    }
}
